import java.net.Socket;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * @author Christer-G
 *
 */
/**
 * @author Christer-G
 *
 */
public class GameThread extends Thread
{
	private int GamesizeX = 0;
	private int GamesizeY = 0;

	private static int numberOfClients = 0;

	private static ArrayList<GameThread> allClients = new ArrayList<GameThread>();

	private final int clientNumber = ++numberOfClients;

	private final PrintWriter out[];
	private final BufferedReader in[];
	private String player1, player2;

	private int[][][] positions;

	private Socket player_socket1;
	private Socket player_socket2;

	public GameThread(Socket player_socket1, Socket player_socket2, int GamesizeX, int GamesizeY) throws IOException {
		this.GamesizeX = GamesizeX;
		this.GamesizeY = GamesizeY;
		this.player_socket1 = player_socket1;
		this.player_socket2 = player_socket2;

		in = new BufferedReader[2];
		in[0] = new BufferedReader(new InputStreamReader(player_socket1.getInputStream()));
		in[1] = new BufferedReader(new InputStreamReader(player_socket2.getInputStream()));

		out = new PrintWriter[2];
		out[0] = new PrintWriter(new BufferedWriter(new OutputStreamWriter(player_socket1.getOutputStream())), true);
		out[1] = new PrintWriter(new BufferedWriter(new OutputStreamWriter(player_socket2.getOutputStream())), true);

		System.out.println("Client thread " + clientNumber + " created.");

		/* Fill the positions (list) with 0 value (and exceptions 'borders')*/
		positions = new int[GamesizeX+1][GamesizeY+3][8];
		for (int row = 0; row < (GamesizeY+3); row++)
			for (int col = 0; col < (GamesizeX+1); col++)
				for (int i = 0; i < 8; i++){

					if(col==0){ // -> Left border
						if(row ==1){ // -> Set upper left corner
							if(i == 0 || i == 1 || i == 2 || i == 3 || i == 4 || i == 5 || i == 6) // Set border cords different
								positions[col][row][i] = 1;
						}
						else if(row == (GamesizeY+1)){ // -> Set bottom left corner
							if(i == 0 || i == 1 || i == 7 || i == 3 || i == 4 || i == 5 || i == 6) // Set border cords different
								positions[col][row][i] = 1;
						}
						else if(i == 0 || i == 1 || i == 3 || i == 5 || i == 6) // Set border cords different
							positions[col][row][i] = 1;
						else
							positions[col][row][i] = 0;
					}

					else if(col==GamesizeX){ // -> Right border
						if(row == 1){ // -> Set upper right corner
							if(i == 0 || i == 1 || i == 2 || i == 3 || i == 4 || i == 7 || i == 6) // Set border cords different
								positions[col][row][i] = 1;
						}
						else if(row == (GamesizeY+1)){ // -> Set bottom right corner
							if(i == 1 || i == 2 || i == 3 || i == 4 || i == 5 || i == 6 || i == 7) // Set border cords different
								positions[col][row][i] = 1;
						}
						else if(i == 1 || i == 2 || i == 4 || i == 6 || i == 7){ // Set border cords different
							positions[col][row][i] = 1;
						}
						else
							positions[col][row][i] = 0;
					}

					else if(row == 1){ // ->> Upper border
						if(col < ((GamesizeX/2)-1)) // -> from corner to ...
							if(i == 0 || i == 1 || i == 2 || i == 3 || i == 4) // Set border cords different
								positions[col][row][i] = 1;
							else
								positions[col][row][i] = 0;

						if(col == (GamesizeX/2)-1) // -> the one close to goal
							if(i == 0 || i == 1 || i == 2 || i == 3) // Set border cords different
								positions[col][row][i] = 1;
							else
								positions[col][row][i] = 0;

						if(col>=((GamesizeX/2)+2))
							if(i == 0 || i == 1 || i == 2 || i == 3 || i == 4) // Set border cords different
								positions[col][row][i] = 1;
							else
								positions[col][row][i] = 0;

						if(col == ((GamesizeX/2)+1))
							if(i == 0 || i == 1 || i == 2 || i == 4) // Set border cords different
								positions[col][row][i] = 1;
							else
								positions[col][row][i] = 0;

						if((col > (GamesizeX/2)-1) && (col < ((GamesizeX/2)+1))) // The cords below Upper goal
							positions[col][row][i] = 0;
					}

					else if(row == (GamesizeY+1)){
						if(col < ((GamesizeX/2)-1)) // -> from corner to ...
							if(i == 3 || i == 4 || i == 5 || i == 6 || i == 7) // Set border cords different
								positions[col][row][i] = 1;
							else
								positions[col][row][i] = 0;

						if(col == (GamesizeX/2)-1) // -> the one close to goal
							if(i == 3 || i == 5 || i == 6 || i == 7) // Set border cords different
								positions[col][row][i] = 1;
							else{
								positions[col][row][i] = 0;
							}

						if(col>=((GamesizeX/2)+2)) 
							if(i == 3 || i == 4 || i == 5 || i == 6 || i == 7) // Set border cords different
								positions[col][row][i] = 1;
							else
								positions[col][row][i] = 0;

						if(col == ((GamesizeX/2)+1))
							if(i == 4 || i == 5 || i == 6 || i == 7) // Set border cords different
								positions[col][row][i] = 1;
							else {
								positions[col][row][i] = 0;
								System.out.println(col + " == " + ((GamesizeX/2)+1));
							}
						if((col > (GamesizeX/2)-1) && (col < ((GamesizeX/2)+1))) // The cords below Upper goal
							positions[col][row][i] = 0;
					}
					else
					{
						positions[col][row][i] = 0;
					}
				}

		System.out.println("Example printout's:");
		System.out.println("This represent (this is an corner)");
		System.out.println("[" + positions[0][1][0] + "][" + positions[0][1][1] + "][" +positions[0][1][2] + "]");
		System.out.println("[" + positions[0][1][3] + "][ ][" +positions[0][1][4] + "]");
		System.out.println("[" + positions[0][1][5] + "][" + positions[0][1][6] + "][" +positions[0][1][7] + "]");

		System.out.println("This represent (this is an corner bottom right)");
		System.out.println("[" + positions[8][11][0] + "][" + positions[8][11][1] + "][" +positions[8][11][2] + "]");
		System.out.println("[" + positions[8][11][3] + "][ ][" + positions[8][11][4] + "]");
		System.out.println("[" + positions[8][11][5] + "][" + positions[8][11][6] + "][" +positions[8][11][7] + "]");

		System.out.println("This represent (upper border row=2)");
		System.out.println("[" + positions[2][1][0] + "][" + positions[2][1][1] + "][" +positions[2][1][2] + "]");
		System.out.println("[" + positions[2][1][3] + "][ ][" +positions[2][1][4] + "]");
		System.out.println("[" + positions[2][1][5] + "][" + positions[2][1][6] + "][" +positions[2][1][7] + "]");

		System.out.println("This represent (upper border row=3)");
		System.out.println("[" + positions[3][1][0] + "][" + positions[3][1][1] + "][" +positions[3][1][2] + "]");
		System.out.println("[" + positions[3][1][3] + "][ ][" +positions[3][1][4] + "]");
		System.out.println("[" + positions[3][1][5] + "][" + positions[3][1][6] + "][" +positions[3][1][7] + "]");

		System.out.println("This represent (lower border row=3 (4))");
		System.out.println("[" + positions[3][11][0] + "][" + positions[3][11][1] + "][" +positions[3][11][2] + "]");
		System.out.println("[" + positions[3][11][3] + "][ ][" +positions[3][11][4] + "]");
		System.out.println("[" + positions[3][11][5] + "][" + positions[3][11][6] + "][" +positions[3][11][7] + "]");

		System.out.println("This represent (upper border row=6 (7))");
		System.out.println("[" + positions[6][1][0] + "][" + positions[6][1][1] + "][" +positions[6][1][2] + "]");
		System.out.println("[" + positions[6][1][3] + "][ ][" +positions[6][1][4] + "]");
		System.out.println("[" + positions[6][1][5] + "][" + positions[6][1][6] + "][" +positions[6][1][7] + "]");

		System.out.println("This represent (upper border row=5)");
		System.out.println("[" + positions[5][1][0] + "][" + positions[5][1][1] + "][" +positions[5][1][2] + "]");
		System.out.println("[" + positions[5][1][3] + "][ ][" +positions[5][1][4] + "]");
		System.out.println("[" + positions[5][1][5] + "][" + positions[5][1][6] + "][" +positions[5][1][7] + "]");

		getGameSize();

		getSetPlayerName();

		allClients.add(this);

		// If any of the above calls throw an
		// exception, the caller is responsible for
		// closing the socket. Otherwise the thread
		// will close it.
		start(); //Starts the thread, and calls run()
	}

	/* getGameSize() send size of the game to client/AI */
	private void getGameSize()
	{
		out[0].println(GamesizeX + "," + GamesizeY);
		out[1].println(GamesizeX + "," + GamesizeY);
	}

	/* getSetPlayerName() send and receive names of the two player to each others*/
	private void getSetPlayerName() throws IOException
	{
		player1 = in[0].readLine();
		out[1].println(player1);
		player2 = in[1].readLine();
		out[0].println(player2);
	}

	/** validateMove() receive a move from player or AI then check it against the int[][][] positions if it is a valid move<p>
	 * Handles every move made by AI or humans, checking if anyone win or get trapped
	 * @param player1 A PrintWriter connected to human player 1
	 * @param player2 A PrintWriter connected to AI / human player 2
	 * @param data Passed argument from the game (client side)
	 * @param player Name of the specific player that called this function
	 * @param in BufferedReader to receive data from player
	 * @throws IOException
	 * @see GameGUI
	 */
	private void validateMove(PrintWriter player1, PrintWriter player2, String data, String player, BufferedReader in) throws IOException
	{
		String split[] = data.split(","); // Split incoming message to tokens

		/* int[][][pos] positions, check witch position it is requesting a check on*/
		int pos;
		if(Integer.parseInt(split[2]) < Integer.parseInt(split[4]) && Integer.parseInt(split[3]) > Integer.parseInt(split[5]))
			pos = 5;
		else if(Integer.parseInt(split[2]) == Integer.parseInt(split[4]) && Integer.parseInt(split[3]) > Integer.parseInt(split[5]))
			pos = 6;
		else if(Integer.parseInt(split[2]) > Integer.parseInt(split[4]) && Integer.parseInt(split[3]) > Integer.parseInt(split[5]))
			pos = 7;
		else if(Integer.parseInt(split[2]) < Integer.parseInt(split[4]) && Integer.parseInt(split[3]) == Integer.parseInt(split[5]))
			pos = 3;
		else if(Integer.parseInt(split[2]) > Integer.parseInt(split[4]) && Integer.parseInt(split[3]) == Integer.parseInt(split[5]))
			pos = 4;
		else if(Integer.parseInt(split[2]) < Integer.parseInt(split[4]) && Integer.parseInt(split[3]) < Integer.parseInt(split[5]))
			pos = 0;
		else if(Integer.parseInt(split[2]) == Integer.parseInt(split[4]) && Integer.parseInt(split[3]) < Integer.parseInt(split[5]))
			pos = 1;
		else
			pos = 2;

		// Setting up some local variables
		int lst_pos = 7-pos % 8;
		Boolean Visited = false;

		/* Check if requested position has been visited before */
		for(int i = 0; i < 8; i++){
			if(positions[Integer.parseInt(split[4])][Integer.parseInt(split[5])][i] == 1)  // Check next position
				Visited = true;
		}

		if(Visited){ //Check if position is a totally new or not!
			if(positions[Integer.parseInt(split[4])][Integer.parseInt(split[5])][pos] == 1){ // Error check
				player1.println("Status: Invalid move!, move to another one.");
				validateMove(player1, player2, in.readLine(), player, in); // This need throw declaration
				return;
			}

			/* Set a value accordingly to what direction from and to nodes */
			positions[Integer.parseInt(split[2])][Integer.parseInt(split[3])][lst_pos] = 1; // Outgoing
			positions[Integer.parseInt(split[4])][Integer.parseInt(split[5])][pos] = 1; 	// Incoming

			/* Return positions to player and AI */
			player1.println("retValid," +  split[2] +","+ split[3] +","+ split[4] +","+ split[5] +","+ split[6] +","+ split[7] +","+ split[8] +","+ split[9]);
			player2.println("draw," +  split[2] +","+ split[3] +","+ split[4] +","+ split[5] +","+ split[6] +","+ split[7] +","+ split[8] +","+ split[9]);

			// Check if player gets trapped
			Boolean trapped = true;
			for(int i = 0; i < 8; i++){
				if(positions[Integer.parseInt(split[4])][Integer.parseInt(split[5])][i] == 0)
					trapped = false;
			}
			/* If player or AI gets trapped send some information and add+show score board */
			if(trapped){
				player1.println("Status: Player (" + player + ") Won, reason (no more moves)!");
				player2.println("Status: Player (" + player + ") Won, reason (no more moves)!");
				player1.println("Score," + player +","+ split[4] +","+ split[4] +","+ GamesizeX/2 +","+ (GamesizeY/2+1));
				player2.println("Score," + player +","+ split[5] +","+ split[5] +","+ GamesizeX/2 +","+ (GamesizeY/2+1));
				addHighscore(player, "1");
				addHighscore(player, "0");
				return;
			}

			/* If all went well, ask the player/AI to make another move*/
			player1.println("Turn"); // We allow the client to make another move
			player1.println("Status: Make another move.");
			validateMove(player1, player2, in.readLine(), player, in); // This need throw declaration
		}

		else{
			if(Integer.parseInt(split[2]) == 0 && Integer.parseInt(split[4]) == 0){ // Catch the user trying to move along the border (left)
				player1.println("Status: Invalid move!, can't move along the border.");
				validateMove(player1, player2, in.readLine(), player, in); // This need throw declaration
				return;
			}

			if(Integer.parseInt(split[2]) == 0 && Integer.parseInt(split[4]) == GamesizeX){ // Catch the user trying to move along the border (right)
				player1.println("Status: Invalid move!, can't move along the border.");
				validateMove(player1, player2, in.readLine(), player, in); // This need throw declaration
				return;
			}

			/* Set a value accordingly to what direction from and to nodes */
			positions[Integer.parseInt(split[2])][Integer.parseInt(split[3])][lst_pos] = 1; // Set outgoing position
			positions[Integer.parseInt(split[4])][Integer.parseInt(split[5])][pos] = 1;

			/* Return positions to player and AI */
			player1.println("retValid," +  split[2] +","+ split[3] +","+ split[4] +","+ split[5] +","+ split[6] +","+ split[7] +","+ split[8] +","+ split[9]);
			player2.println("draw," +  split[2] +","+ split[3] +","+ split[4] +","+ split[5] +","+ split[6] +","+ split[7] +","+ split[8] +","+ split[9]);

			/* If all went well, ask the player/AI the other player to make a move */
			player1.println("Status: Player (" + player + ") turn to move");
			player2.println("Status: Player (" + player + ") turn to move");
			player2.println("Turn"); // Set the next client to make its move
		}

		/* Check if position is at goal (upper) */
		if(Integer.parseInt(split[5]) == 0){
			player1.println("Status: Player (" + this.player1 + ") Won!");
			player2.println("Status: Player (" + this.player1 + ") Won!");
			player1.println("Score," + this.player1 +","+ split[4] +","+ split[5] +","+ GamesizeX/2 +","+ (GamesizeY/2+1));
			player2.println("Score," + this.player1 +","+ split[4] +","+ split[5] +","+ GamesizeX/2 +","+ (GamesizeY/2+1));
			addHighscore(this.player1, "1");
			addHighscore(this.player2, "0");
			return;
		}

		/* Check if position is at goal (lower) */
		if(Integer.parseInt(split[5]) == GamesizeY+2){
			player1.println("Status: Player (" + this.player2 + ") Won!");
			player2.println("Status: Player (" + this.player2 + ") Won!");
			player1.println("Score," + this.player2 +","+ split[4] +","+ split[5] +","+ GamesizeX/2 +","+ (GamesizeY/2+1));
			player2.println("Score," + this.player2 +","+ split[4] +","+ split[5] +","+ GamesizeX/2 +","+ (GamesizeY/2+1));
			addHighscore(this.player2, "1");
			addHighscore(this.player1, "0");
			return;
		}
	}

	/** Add User(s) to high score if not already in it, and updates their score if they played before<p>
	 * function is called from validateMove()
	 * @param player name of player to search for
	 * @param score the score to be set to player
	 * @throws IOException
	 * @see validateMove
	 */
	private void addHighscore(String player, String score) throws IOException
	{
		try {		
			FileInputStream fstream = new FileInputStream(System.getProperty("user.dir") + "\\save.data");
			BufferedReader change = new BufferedReader(new InputStreamReader(fstream));

			String strLine;
			StringBuilder fileContent = new StringBuilder();
			Boolean userExist = false;

			/* Searching for user and updates it if it is found */
			while ((strLine = change.readLine()) != null) {
				String tokens[] = strLine.split(",");
				if (tokens[0].equals(player)) {
					tokens[1] = "" + (Integer.parseInt(tokens[1]) + Integer.parseInt(score));
					tokens[2] = "" + (Integer.parseInt(tokens[2]) + 1);
					String newLine = tokens[0] + "," + tokens[1] + "," + tokens[2];
					fileContent.append(newLine);
					fileContent.append("\n");
					userExist = true;
				} else {
					fileContent.append(strLine); // update content as it is
					fileContent.append("\n");
				}
			}

			/* If user does not exist, creates it here */
			if(!userExist)
			{
				String newUserLine = player + "," + (0 + Integer.parseInt(score)) + "," + 1;
				fileContent.append(newUserLine);
				fileContent.append("\n");
			}

			// Now fileContent will have updated content, then we print it back to the score file
			FileWriter fstreamWrite = new FileWriter(System.getProperty("user.dir") + "\\save.data");
			BufferedWriter out = new BufferedWriter(fstreamWrite);
			out.write(fileContent.toString());
			out.close();
			//Close the input stream
			change.close();

			// Showing the score board for humans
			showHighscore(player, fileContent.toString());

		} catch (Exception e) { //Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}
	}

	
	/* showHighscore(...) display score board for human players */
	private void showHighscore(String player, String data)
	{
		/*Send out score board to humans, this prevent to send 2 boards */
		if(player.equals(player1))
			out[0].println("highscore,#Player,Won,Played," + data.replace('\n', ','));
		else
			out[1].println("highscore,#Player,Won,Played," + data.replace('\n', ','));
	}

	/* The heart of our GameThread, the flow of the game */
	public void run() {

		try {
			String command_player1, command_player2;
			out[0].println("Status: Player (" + player1 + ") turn to move"); // Send first status message to both players
			out[1].println("Status: Player (" + player1 + ") turn to move");
			out[0].println("Turn"); // Send to first client to know it is its turn to move (block move from the other client / AI)

			while (true) {
				command_player1 = in[0].readLine(); // Read command sent from client 1
				if(command_player1.startsWith("validate")){
					validateMove(out[0], out[1], command_player1, player2, in[0]);
				}

				command_player2 = in[1].readLine(); // Read command sent from client 2 or AI
				if(command_player1.startsWith("validate")){
					validateMove(out[1], out[0], command_player2, player1, in[1]);
				}
			} //while
		}
		/* Ending of GameThread */
		catch(IOException e) {
			System.out.println("Client thread " + clientNumber + ": I/O error");
		}
		finally {
			try { 
				player_socket1.close();
				player_socket2.close();
			}
			catch(IOException e) {
				System.out.println("Client thread " + clientNumber + ": Socket not closed!");
			}
			allClients.remove(allClients.indexOf(this));
		} //finally
	} //run
} //class ClientThread